#include "unitfunctor.h"

const string UnitFunctor::cartisiansymbol = "CartesianCoordinates";
const string UnitFunctor::atomsymbol = "Atom";
const string UnitFunctor::bondtypessymbol = "BondType";
const string UnitFunctor::uniquebondssymbol = "UniqueBonds";

const string UnitFunctor::inplanesymbol = "Inplane";
const string UnitFunctor::outofplanesymbol = "Outofplane";

UnitFunctor::UnitFunctor(System* s, const rule::list& opts) : 
	sys(s),
	cartisian(false),
	atomtype(allatoms),
	atomindex(0),
	bonds(inplane),
	uniquebonds(true)
{
	const size_t rulesize = opts.size();
	for(size_t r = 0; r < rulesize; r++)
	{
		if(!opts[r].first.isSymbolType())
			continue;
		
		symbol sym = opts[r].first.toSymbol();
		variant var = opts[r].second;

		if(sym.name() == cartisiansymbol)
			cartisian = (var.isBoolType() && var.toBool()) ? true : false;
		else if(sym.name() == atomsymbol)
		{
			atomtype = (var.isNumericType() && var.toLong() > 0) ? index : allatoms;
			if(atomtype == index)
				atomindex = var.toSize();
		}
		else if(sym.name() == bondtypessymbol && var.isSymbolType())
		{
			if(var.toSymbol().getType() == symbol::predefined && var.toSymbol().reference() == symbol::all)
				bonds = allbonds;
			else if(var.toSymbol().getType() == symbol::userdefined && var.toSymbol().name() == inplanesymbol)
				bonds = inplane;
			else if(var.toSymbol().getType() == symbol::userdefined && var.toSymbol().name() == outofplanesymbol)
				bonds = outofplane;
		}
		else if(sym.name() == uniquebondssymbol && var.isBoolType())
		{
			uniquebonds = var.toBool();
		}
	}
}

variant UnitFunctor::getAtoms(int id) const
{
	Unit* unitref = sys->getUnit(id);
	if(!unitref)
		return variant();

	// the result is a varaint list
	variant::list resultlist;

	const size_t size = unitref->getAtoms().size();
	resultlist.resize(size);

	for(size_t a = 0; a < size; a++)
	{
		if(cartisian)
			resultlist[a] = unitref->getAtoms()[a].xyz();
		else
			resultlist[a] = unitref->getAtoms()[a].toIVec4();
	}

	return resultlist;
}

variant UnitFunctor::getPeriodicity(int id) const
{
	Unit* unitref = sys->getUnit(id);
	if(!unitref)
		return variant();
		
	if(cartisian)
		return unitref->getPeriodicityXyz();
	else
		return unitref->getPeriodicity();
}
mat UnitFunctor::getReciprocity(int id) const
{
	Unit* unitref = sys->getUnit(id);
	if(!unitref)
		return mat();
		
	return unitref->getReciprocity();
}
mat UnitFunctor::getBrillouin(int id) const
{
	Unit* unitref = sys->getUnit(id);
	if(!unitref)
		return mat();
		
	return unitref->getBrillouin();
}
void UnitFunctor::setPeriodicity(int id, const imat& per) const
{
	Unit* unitref = sys->getUnit(id);
	if(!unitref)
		return;

	unitref->setPeriodicity(per);
}
size_t UnitFunctor::getAtomCount(int id) const
{
	Unit* unitref = sys->getUnit(id);
	if(!unitref)
		return 0;

	return unitref->getAtoms().size();
}
variant UnitFunctor::getBonds(int id) const
{
	Unit* unitref = sys->getUnit(id);
	if(!unitref)
		return variant();
	
	const Bond::table &bondtable = unitref->getBonds();
	const umat &bondloc = unitref->getBondsLocations();
	
	// the result is a varaint list
	variant::list resultlist;
	
	const size_t size = unitref->getAtoms().size();
	const size_t elementsize = bondtable.size();


	
	if(atomtype != index)
		resultlist.reserve(size*10);
	else
		resultlist.reserve(10); // There is always max 10 bonds.

	for(size_t e = 0; e < elementsize; e++)
	{
		if(atomtype == index && bondloc(0,e) != atomindex -1)
			continue;
		else if(uniquebonds && atomtype != index && bondloc(0,e) > bondloc(1,e))
			continue;

		size_t bondsize = bondtable[e].size();
		for(size_t b = 0; b < bondsize; b++)
		{

			if(bondtable[e][b].getCouplingType() == Bond::SameAtom)
				continue;
			else if(bonds == inplane && bondtable[e][b].getCouplingType() != Bond::NearestNeighbours)
				continue;
			else if(bonds == outofplane && bondtable[e][b].getCouplingType() == Bond::NearestNeighbours)
				continue;

			variant::list bondpair;
				
			if(cartisian)
			{
				bondpair.push_back(bondtable[e][b].getOrigin()->xyz());
				bondpair.push_back(vec3((bondtable[e][b].getOrigin()->xyz() + bondtable[e][b].getXyz())));
				resultlist.push_back(bondpair);
				continue;
			}
			
			bondpair.push_back((size_t)bondloc(0,e)+1);
			bondpair.push_back((size_t)bondloc(1,e)+1);
			resultlist.push_back(bondpair);
			break;
		}
	}

	return resultlist;
}

bool UnitFunctor::exists(int id) const
{
	Unit* unitref = sys->getUnit(id);
	if(!unitref)
		return false;
	
	return true;
}
bool UnitFunctor::initialized(int id) const
{
	Unit* unitref = sys->getUnit(id);
	if(!unitref)
		return false;
	
	return unitref->isInitilized();
}
bool UnitFunctor::initialize(int id) const
{
	Unit* unitref = sys->getUnit(id);
	if(!unitref)
		return false;

	unitref->initialize();
	
	return unitref->isInitilized();
}